﻿using System;
using System.Windows.Forms;
using Auxiliary.VecMath;
using Simple3DRedactor.Domain;
using Tao.OpenGl;

namespace Simple3DRedactor.OpenGLViewer
{
    class TopRenderer : Renderer
    {
        private double _x;
        private double _y = 10.0;
        private double _z;

        private bool _isCameraMoving;
        private bool _isObjectMoving;

        private int _oldMx;
        private int _oldMy;

        private const double PosStep = 1.0;

        public TopRenderer(OpenGLViewer viewer) : base(viewer)
        {
        }
        
        public override void LoadProjectionMatrix(int width, int height)
        {
            Glu.gluPerspective(60.0f, (double)width / height, 0.1, 100.0);
        }

        public override void LoadModelViewMatrix()
        {
            Glu.gluLookAt(
                   _x, _y, _z,
                   _x, 0.0, _z,
                   0.0, 0.0, -1.0);
        }

        public override void Show(SceneView<IOpenGLObjectView> scene)
        {
            Gl.glDisable(Gl.GL_DEPTH_TEST);
            Gl.glDisable(Gl.GL_LIGHTING);
            
            Gl.glBegin(Gl.GL_LINES);
                // X axis
                Gl.glColor3f(1.0f, 0.0f, 0.0f);
                Gl.glVertex3i(-1000, 0, 0);
                Gl.glVertex3i(1000, 0, 0);
            
                // Z axis
                Gl.glColor3f(0.0f, 0.0f, 1.0f);
                Gl.glVertex3i(0, 0, -1000);
                Gl.glVertex3i(0, 0, 1000);
            Gl.glEnd();

            Gl.glPolygonMode(Gl.GL_FRONT_AND_BACK, Gl.GL_LINE);

            RenderScene(scene);
        }

        public override void OnMouseDown(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                _oldMx = e.X;
                _oldMy = e.Y;

                ClearSelection();

                if (TrySelectObject(e.X, e.Y))
                {
                    _isCameraMoving = false;
                    _isObjectMoving = true;
                }
                else
                {
                    _isCameraMoving = true;
                    _isObjectMoving = false;
                }
            }
        }

        private void CalcMouseCoord(int mx, int my, double y, out double x, out double z)
        {
            var viewport = new int[4];
            Gl.glGetIntegerv(Gl.GL_VIEWPORT, viewport);

            SetupCamera(viewport[2], viewport[3]);

            var modelMatrix = new double[16];
            Gl.glGetDoublev(Gl.GL_MODELVIEW_MATRIX, modelMatrix);

            var projectionMatrix = new double[16];
            Gl.glGetDoublev(Gl.GL_PROJECTION_MATRIX, projectionMatrix);

            double x0, y0, z0;
            Glu.gluUnProject(mx, my, 0.0, modelMatrix, projectionMatrix, viewport, out x0, out y0, out z0);

            double x1, y1, z1;
            Glu.gluUnProject(mx, my, 1.0, modelMatrix, projectionMatrix, viewport, out x1, out y1, out z1);

            x = (y - y0) / (y1 - y0) * (x1 - x0) + x0;
            z = (y - y0) / (y1 - y0) * (z1 - z0) + z0;
        }

        public override void OnMouseMove(MouseEventArgs e)
        {
            if (_isCameraMoving)
            {
                double oldx, oldz;
                CalcMouseCoord(_oldMx, _oldMy, 0, out oldx, out oldz);

                double newx, newz;
                CalcMouseCoord(e.X, e.Y, 0, out newx, out newz);

                _x += oldx - newx;
                _z += newz - oldz;
            }
            else if (_isObjectMoving)
            {
                double oldx, oldz;
                CalcMouseCoord(_oldMx, _oldMy, SelectedObject.InnerObject.Position.Y, out oldx, out oldz);

                double newx, newz;
                CalcMouseCoord(e.X, e.Y, SelectedObject.InnerObject.Position.Y, out newx, out newz);

                SelectedObject.Move(new Vector3D((float) (newx - oldx), 0, (float) (oldz - newz)));
            }

            _oldMx = e.X;
            _oldMy = e.Y;
        }

        public override void OnMouseUp(MouseEventArgs e)
        {
            _isCameraMoving = false;
            _isObjectMoving = false;
        }

        public override void OnMouseWheel(MouseEventArgs e)
        {
            _y -= PosStep * Math.Sign(e.Delta);
        }

        public override void OnKeyDown(KeyEventArgs e)
        {
            if (SelectedObject != null)
            {
                switch (e.KeyCode)
                {
                    case Keys.W:
                        SelectedObject.Rotate(new Vector3D(-1.0f, 0.0f, 0.0f));
                        break;
                    case Keys.S:
                        SelectedObject.Rotate(new Vector3D(1.0f, 0.0f, 0.0f));
                        break;
                    case Keys.A:
                        SelectedObject.Rotate(new Vector3D(0.0f, 0.0f, 1.0f));
                        break;
                    case Keys.D:
                        SelectedObject.Rotate(new Vector3D(0.0f, 0.0f, -1.0f));
                        break;
                }
            }
        }
    }
}
